chore(scripts): measure a strict authoring face over today's corpora (objectui#7581) - #7916
Merged
baozhoutao merged 1 commit intoSep 6, 2026
Conversation
…(objectui#7581) The first deliverable of the #5250 ruling is a number, not a fix. This adds a reproducible throwaway that derives a strict twin of every node schema in `@object-ui/types/zod` (in memory — no schema changes, no gate wiring, and `.passthrough()` on `BaseSchemaCore` is untouched) and runs three corpora through it, counting undeclared keys per component. The traversal is schema-guided rather than "any object with a `type` key": a field definition `{ name: 'amount', type: 'currency' }` and a node `{ type: 'text' }` are structurally identical and half the field-type vocabulary collides with a node type, so nodes are found only where the schema declares a `SchemaNodeSchema` slot. The registry is taken from the arms of `AnyComponentSchema` for the same reason: an export-order registry hands the `text` and `select` node types to `FilterFieldSchema`. Output is deterministic and pins the `main` SHA it ran against, so the numbers can be re-derived after the declaration repairs of #6939 land and move them. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013uAaxiwgYDybsTNV9xwa1M
This was referenced Sep 6, 2026
baozhoutao
marked this pull request as ready for review
September 6, 2026 03:03
This was referenced Sep 6, 2026
baozhoutao
deleted the
claude/issue-7581-strict-authoring-face-measurement
branch
September 6, 2026 03:35
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #7581
The first deliverable of the #5250 ruling (maintainer, 2026-09-04, batch #25, option 2 — a strict authoring face as a programme, measurement first). This PR ships nothing and repairs nothing: no schema in
packages/typeschanges, no gate is wired, and.passthrough()onBaseSchemaCoreis untouched. It adds one reproducible throwaway,scripts/measure-strict-authoring-face.mjs, plus the reading it produces.The number, in one line
2100 nodes across the three corpora. 196 are refused by a strict twin; 137 of those are green today and red only under strict; the other 59 are already red under the face as shipped. 58 of the 94 component types seen are strict-clean — zero refusals. A naive
.strict()flip over whole documents refuses 304 of 557 node documents, but that number measures the recursion point, not the components (see "the node boundary" below).How to re-derive it
Output is deterministic — every list sorted, nothing timestamped — so two runs diff clean (verified, both formats). The report pins HEAD, the
origin/maincommit it forked from, and a mechanical answer to "does this tree's corpus still equal main's?", so the numbers can be re-derived after the declaration repairs of #6939 land and move them. #6939 was not merged in: this reading isfa7d66c45's.The five decisions the measurement rests on
The registry is the arms of
AnyComponentSchema(107 component types, zero collisions), not a type map built from all 206 module exports. That is not a style preference — it is measurably load-bearing:FilterFieldSchemadeclarestype: z.enum(['text','select','number','date',…])for a filter field, and an export-order registry hands it thetextandselectnode types. The first draft of this script did exactly that and reported 718textnodes with 704 strict refusals; the corrected registry reports 637textnodes with 0.Traversal is schema-guided, never "any object with a
typekey". A field definition{ name: 'amount', type: 'currency' }and a node{ type: 'text' }are structurally identical, and half the field-type vocabulary (text,number,date,select,html,label) collides with a node type. A value is a node only where the schema declares aSchemaNodeSchemaslot; from there descent continues through that node's own schema. Atypeat a node slot resolving in no schema is counted, not dropped.The node boundary. Child slots are
z.union([SchemaNodeSchema, z.array(SchemaNodeSchema)]), andSchemaNodeSchemaresolves toBaseSchemaCore— the ~21 base keys and nothing type-specific. Strict-ifying that recursion point makes every child node's own declared props unrecognised, so it measures the recursion point rather than the components. The per-component tables therefore replaceSchemaNodeSchemawithz.any()and flatten each document into nodes judged against their own schemas. Both readings are reported; the whole-tree one (304/557) is what a naive flip would really do, and it is the programme's first structural finding: the recursion point must be redirected toAnyComponentSchemabefore strict can be switched on at all.Three twins per schema.
baseline(the face as shipped, boundary applied) tells you what is red today;strictaddscatchall: nevereverywhere;keyProbeis a shape skeleton — same keys, every leafz.any(), every member optional, union arms merged key-wise — that cannot fail, sodiff(input, keyProbe.parse(input))enumerates undeclared keys exactly, including on nodes that are red for value reasons and would otherwise yield no parse output to diff. Objects are cloned by patching_zod.defand calling their own constructor rather than rebuilt withz.object(shape), because the latter drops.refine()checks and a twin that quietly lost a refinement would under-report red.The predicates, stated because the card asks for them. Node document: parses, is a plain object, carries a string
type, and thattyperesolves in the registry. Fragment (docs only): a```jsonfence whose body does not parse as JSON — the deliberate-snippet shape; counted, never silently skipped, and this count is the price tag on finding: no gate reads a doc snippet's schema KEYS against the spec — #4823's deferred "second dimension", closed unbuilt #5138. Non-document: parses but fails one of the other three, split intonotAnObject/noTypeKey/unresolvedType— the last kept apart because such a document is refused byAnyComponentSchematoday.Corpus enumeration — mine beside the PM's
examples/schema-catalog/src/schemas/**/*.json```jsonfence openings undercontent/docs/**apps/**/*.jsonpackage.json, threetsconfig*.json,manifest.json,vercel.json): zero authored node documentspackages/*/examples/**.json, 5.tsapps/**/*.jsonholds no authored documents, corpus 3 was widened mechanically to.tsmodules underapps/**andpackages/*/examples/**that carry atype: '…'literal in source; those are imported and their exported plain objects harvested. That yields 19 items and only 2 node documents. The apps' real authored surface is JSX inside*-preview.tsxharnesses and metadata-designer drafts inpreview-samples.tsthat are spec records (objects, tools, jobs), not node documents. One module could not be imported and is recorded rather than skipped.Cross-reference against the cards named on #7581
safeValidateSchemabecause the Zod mirror names a key its renderer never reads (8 components) #6939 (open, in flight) — "24 catalog entries failsafeValidateSchema". Its 8 components (tooltip,context-menu,tree-view,kanban,filter-builder,chart, +2) are a subset of what the already-red-today column here reports: 20 whole catalog documents red today at the root, 23 catalog nodes red today, and a wider component set (resizable,carousel,flex,page,badge,select,list-view,object-grid,object-form,data-table,detail,grid,icon,list,alert,collapsible,button,form,dashboard) once nested nodes are judged too. ⛔ Nothing here was repaired.ChartSchemadeclares neither chart-leveldatanorxAxisKey— yet its own JSDoc teaches both, so the corrected chart model documents keys onlyBaseSchema's index signature admits #7549 (closed) —ChartSchemadeclares neither chart-leveldatanorxAxisKey. The same class shows up on a different owner here:dashboard→widgets.[].options.data×11,widgets.[].options.xField×11,widgets.[].options.yField×11. Different keys, different schema; listed so triage can tell them apart.button-groupimplements no selection, and never wires the per-buttononClick/disabledit declares — the catalog authors 29 keys nothing reads #7077 (closed) — the "key nothing reads" retirement shape. The renderer-mention probe below is the mechanical first cut at that split; its zero-mention rows (columnSpan,fullWidth,itemTemplate,rowSpan,summary_field,summary_object,totalItems) are the finding(components):button-groupimplements no selection, and never wires the per-buttononClick/disabledit declares — the catalog authors 29 keys nothing reads #7077-shaped candidates.variant: "destructive", a key no arm ofMenuItemdeclares and no menu renderer reads — but the repair has a real fork #7101 (closed) —variant: "destructive"on a catalog menu item.variantstill appears here as an undeclared key ×8 (badge×2 among others), so the class is not exhausted; ⛔ not repaired.typekey, out of 204.Findings filed separately (⛔ not repaired here)
Two things the measurement surfaced that are not undeclared-key results, deduped against the open backlog before filing:
BreadcrumbSchemaandObjectTreeSchemaare not exported by name from@object-ui/types/zod— reachable only through the union, so no consumer can validate one alone.z.lazyexports rebuild their schema on every getter call (getter() === getter()isfalse), so the schema is reconstructed per parse and cannot be compared by identity.Everything else is listed below rather than filed, which #7581 explicitly permits ("file it … or list it in the report").
Gates
pnpm check:control-bytes✅ check-control-bytes: OK (scanned 6398 tracked text file(s); skipped 85 binary).— exit 0pnpm check:shell-escape-residue✅ check-shell-escape-residue: OK (5/5 root(s) resolved …; 0 occurrence(s) outside a fence …)— exit 0pnpm type-check:scriptstsc -p tsconfig.scripts.json)pnpm lint:root✖ 33 problems (0 errors, 33 warnings)— exit 0; 0 of them on the new file (pre-existing warnings only)node scripts/check-governed-queue-guard.mjs --test …✅ NOT GOVERNED — 2 path(s) checked against 5 governed surface(s); none matched.--json, byte-identical both waysThe corpus pin has a verified negative leg: appending a line to one
content/docsfile flipscorpus identical to that main committo NO, andgit checkout HEAD -- content/docsrestores the file byte-identically (blob6aa5e59→1d59f6a→6aa5e59,git diff HEAD -- content/docsempty).Strict authoring face — measurement (objectui#7581)
0044a60218eba4993b0a6e64e8697068c98756c4· forked fromorigin/mainatfa7d66c4505d0fcc866fbecec1901c4990a80c3amaincommit: yes (compared overexamples/schema-catalog/src/schemas,content/docs,apps,packages/types/src)4.4.3· face read frompackages/types/dist/zod/index.zod.jsAnyComponentSchema): 107 component typesPer corpus
typetypeHeadline
Top undeclared keys — top level (what
BaseSchemaCore.passthrough()admits today)showSubmitactiondataSourceactionschangeformTypemessagesectionssubmitButtontextvaluecurrentPagedefaultViewTypelistViewssearchPlaceholdersearchableFieldsseparatortitlebreadcrumbscontentdefaultListViewdefaultTabemptyTexteventsfieldfilterableFieldsfullWidthiconitemTemplateitemsnavigationoperationsoptionsFrompageSizepagespaginationrequiredresizableColumnsrowsshowStepIndicatorTop undeclared keys — nested inside declared members
widgets.[].valuewidgets.[].iconwidgets.[].trendwidgets.[].trendValuewidgets.[].options.datawidgets.[].options.xFieldwidgets.[].options.yFielditems.[].descriptionitems.[].timeitems.[].titleitems.[].variantfields.[].multiplefields.[].rowsitems.[].iconwidgets.[].options.valuefields.[].columnsfields.[].formulafields.[].precisionfields.[].return_typefields.[].summary_fieldfields.[].summary_objectfields.[].summary_typeitems.[].labelitems.[].typewidgets.[].colSpanwidgets.[].rowSpanfields.[].currencyfields.[].dimensionsfields.[].formatfields.[].maxfields.[].minfields.[].reference_toitems.[].itemscolumns.[].keycolumns.[].titlefields.[].acceptfields.[].columnSpanfields.[].defaultCheckedfields.[].defaultValuefields.[].maxLengthUndeclared keys × does any renderer source mention the identifier?
Coarse triage hint only — see the
rendererMentionsdocblock. A key with 0mentions is #7077-shaped (nothing reads it); a key with mentions is #6939-shaped
(the renderer reads it, the schema does not declare it). Both still need the call
site read before they become worklist items.
Probe controls —
className(must be > 0): 13409;zzzFabricatedControlKey(must be 0): 0 ⇒ probe is live.git grep -whits inpackages/*/src(types excluded)columnSpanfullWidthitemTemplaterowSpansummary_fieldsummary_objecttotalItemsdefaultCheckedreturn_typesummary_typestripedxFieldoptionsFrombreadcrumbsmin_lengthdefaultListViewdrawerSideshowStepIndicatorsingleClickEdittrendValueresizableColumnsshowViewSwitcherfilterableFieldsdefaultViewTypeyFielddefaultTabvalidationssearchableFieldscurrentPageminLengthshowSubmitsubmitButtoncolSpanemptyTextformTypetrendprecisionlistViewssearchPlaceholdermaxLengthpaginationreference_topageSizeoperationsseparatorpatternformulaacceptdimensionsmultiplepageseventscurrencysectionsformatenablednavigationchangecontenttimerequiredmaxactionsminicondefaultValuemodevariantdataSourcecolumnsbodydescriptionrowsmessageactiontitleitemsdataschemakeyfieldtextvaluelabeltypeWhy the nodes that are ALREADY red today are red (top issues)
These are refused by the face as shipped; strict is not what broke them.
resizable: panels.0.id — invalid_typeresizable: panels.1.id — invalid_typecarousel: items.0 — invalid_typecarousel: items.1 — invalid_typecarousel: items.2 — invalid_typeflex: direction — invalid_valuepage: body — invalid_typebadge: variant — invalid_valuedashboard: (root) — unrecognized_keysdashboard: type — invalid_valuefilter-builder: conditions — invalid_typefilter-builder: field — invalid_typefilter-builder: logic — invalid_valuefilter-builder: operator — invalid_valuebutton: onClick — customform: onSubmit — customlist-view: objectName — invalid_typeobject-grid: objectName — invalid_typeresizable: panels.2.id — invalid_typeselect: options.0 — invalid_typeselect: options.1 — invalid_typeselect: options.2 — invalid_typealert: variant — invalid_valuebutton: size — invalid_valuecarousel: items.3 — invalid_typecollapsible: content — invalid_typedata-table: columns.0.accessorKey — invalid_typedata-table: columns.0.header — invalid_typedata-table: data — invalid_typedetail: groups.0.fields.1.type — invalid_valueform: fields — invalid_typeform: fields.1.validation.pattern — invalid_typeform: fields.2.options.0 — invalid_typeform: fields.2.options.1 — invalid_typeform: fields.2.options.2 — invalid_typegrid: (root) — invalid_typeicon: icon — invalid_typelist: items — invalid_typeobject-form: mode — invalid_valueobject-form: objectName — invalid_typePer component type
textflexstackitems×1buttonaction×5,events×1,fullWidth×1,text×1cardchange×3,value×3,dataSource×2,content×1,icon×1formshowSubmit×76,submitButton×3,title×1fields.[].multiple×6,fields.[].rows×5,fields.[].columns×3,fields.[].formula×3,fields.[].precision×3,fields.[].return_type×3, +19 moreiconboxlabelrequired×1badgetext×2inputvalidations×1statisticgridactions×1,pagination×1,rows×1,title×1object-griddataSource×2,operations×1,resizableColumns×1,singleClickEdit×1,striped×1pagination.enabled×1,selection.enabled×1,selection.mode×1containerseparatortoggledashboardwidgets.[].value×16,widgets.[].icon×15,widgets.[].trend×15,widgets.[].trendValue×15,widgets.[].options.data×11,widgets.[].options.xField×11, +4 moreavatarpageactions×3,breadcrumbs×1regions.[].body×1divselectfield×1,optionsFrom×1checkboxdata-tabledataSource×1columns.[].key×1,columns.[].title×1radio-groupdate-pickeremptyaction×4,message×2file-uploadheader-barresizablescroll-areasonnerspinnerswitchtoastkanbantree-viewaspect-ratiobutton-groupcarouselcomboboxsearchPlaceholder×2,emptyText×1filter-builderinput-otpseparator×2kbdobject-formformType×3,sections×3,dataSource×1,defaultTab×1,showStepIndicator×1progresstabletimelineitems.[].description×8,items.[].time×8,items.[].title×8,items.[].variant×8,items.[].icon×4,items.[].label×3alert-dialogactions×4bar-chartbreadcrumbchartcode-editorobject-viewdefaultViewType×2,listViews×2,searchableFields×2,defaultListView×1,filterableFields×1,navigation×1, +1 moreform.drawerSide×1,form.formType×1textareaalertmessage×1calendar-viewchatbotlistitemTemplate×1items.[].type×3markdownobject-ganttobject-mapsheetskeletontabstoastertoggle-groupcommanddropdown-menuimagelist-viewdataSource×2loadingnavigation-menuitems.[].items×2paginationcurrentPage×2,pageSize×1,totalItems×1view-switcheraccordionactionapppages×1collapsiblecontext-menudetaildetail-viewdialogdrawerfilter-uihover-cardhtmlmenubarobject-kanbandataSource×1popoversidebarslidersort-uitooltiptypevalues that resolve in no node schema (refused byAnyComponentSchematoday)At a document root:
ui:calendarsidebar-provideraction:buttonarticlehomeitemmodulepage-headerscriptsectionasideboardcomponent-namedonut-chartfootergroupheadermainmy-componentnavobjectpage:tabspie-chartradar-chartschedulespec-reportsummaryunknown-componentAt a declared child-node slot:
footerh1headerline-chartmainmy-componentnavpasswordui:calendarRegistered component types with zero documents in any corpus
calendar,chatbot-enhanced,chatbot-floating,crud-dialog,object-calendar,object-chart,object-data-table,object-gallery,object-tree,report,report-builder,report-viewer,spanNode schemas not exported by name from
@object-ui/types/zodbreadcrumb,object-tree— reachable only through the union, so no consumer can validate one of these alone.z.lazyexports whose getter rebuilds the schema on every callActionSchema,AppMenuItemSchema,FilterBuilderConditionSchema,FilterGroupSchema,MenuItemSchema,NavLinkSchema,NavigationItemSchema,NavigationMenuItemSchema,SchemaNodeSchema,TreeNodeSchema—getter() === getter()is false, so the schema is reconstructed on every parse and no consumer can compare it by identity.The TypeScript face — where
[key: string]: anyis declaredpackages/types/srcdeclares the index signature at 12 sites, and 120 interfacesextends BaseSchemadirectly (so they inherit it).packages/types/src/base.tsBaseSchemapackages/types/src/base.tsComponentRendererPropspackages/types/src/complex.tsKanbanCardpackages/types/src/data-protocol.tsQueryASTNodepackages/types/src/data.tsQueryParamspackages/types/src/data.tsAggregateResultpackages/types/src/designer.tsUnifiedViewConfigpackages/types/src/designer.tsDashboardConfigpackages/types/src/form.tsFormFieldpackages/types/src/objectql.tsListViewGalleryConfigpackages/types/src/objectql.tsListViewTimelineConfigpackages/types/src/ui-action.tsActionContextWalker limits — shapes strict could not close
customnode — strict cannot reach inside it: (inline custom)functionnode — strict cannot reach inside it: (inline function)transformnode — strict cannot reach inside it: (inline transform)Authored-module loading (corpus 3)
apps/console/src/pages/developer/hooks/useApiDiscovery.ts: Cannot find module '/home/user/objectui-issue-7581/apps/console/node_modules/@object-ui/app-shell/dist/index.js' imported from /home/user/objectui-issue-7581/apps/console/src/pages/developer/hooks/useApiDiscovery.tsModules that PRINT when imported (console muted for the run):
packages/types/examples/zod-validation-example.ts🤖 Generated with Claude Code
https://claude.ai/code/session_013uAaxiwgYDybsTNV9xwa1M
Generated by Claude Code